package hc.server.ui.design.hpj;

import hc.core.IConstant;
import hc.core.L;
import hc.core.util.HCURL;
import hc.core.util.LogManager;
import hc.server.AbstractDelayBiz;
import hc.server.DelayServer;
import hc.server.J2SEContext;
import hc.util.ResourceUtil;

import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.script.Bindings;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleBindings;
import javax.swing.JOptionPane;

public class RubyExector {
	private static boolean isError = false;
	private static final RubyWriter errorWriter = new RubyWriter();
	private static Pattern linePattern = Pattern.compile("<script>:(\\d+)");
	
	public static final void findClass(){
//		>> $CLASSPATH<<'target/classes'
//		=> 
//		["file:/C:/dev/digitalarchive_trunk/dax/tapeingestreader/target/classes/"]
//		>> import org.familysearch.digitalarchive.tapeingestreader.Boolean2
//
//		(where file 
//		"target/classes/org/familysearch/digitalarchive/tapeingestreader/Boolean2.class" 
//		exists)
	}
	
	public static final void findJar(){
//		From jar files
//
//		To use resources within a jar file from JRuby, the jar file must either be on the classpath or be made available with the require method:
//		require 'path/to/mycode.jar'
//		 This require makes the resources in mycode.jar discoverable by later commands like import and include_package. 
//
//		Note that loading jar-files via require searches along the $LOAD_PATH for them, like it would for normal ruby files.
	}
	
//	https://github.com/jruby/jruby/wiki/RedBridge
//	Scope: Per Container
//	 Property Name: org.jruby.embed.localcontext.scope
//	 Value: singleton(default in JRuby 1.6.x/1.5.x/1.4.0 and JRuby trunk; rev. 9e557a2 and later),
//	 theadsafe (default in JRuby 1.4.0RC1, RC2, RC3),
//	 singlethread, 
//	 or concurrent (since JRuby 1.6.0)
	
	
//	Scope: Per Container
//	 Property Name: org.jruby.embed.compilemode
//	 Value: off (default), jit, or force
//	JRuby has jit and force options to compile Ruby scripts. This configuration provides users a 
//	way of setting a compile mode. When jit or force is specified, only a global variable 
//	can be shared between Ruby and Java.
	
//		System.setProperty("jruby.home", "/Users/yoko/Tools/jruby-1.3.1");
//		System.setProperty("org.jruby.embed.localcontext.scope", "singleton");
//		System.setProperty("org.jruby.embed.localcontext.scope", "concurrent");
//		System.setProperty("org.jruby.embed.localvariable.behavior", "transient");//"persistent", "global"
		
	public static final void setLoadPath(String path){
		System.setProperty("org.jruby.embed.class.path", path);
		
		//Be sure to set classpaths before you instantiate javax.script.ScriptEngineManager
		buildEngine();

		/*
//		Scope: Per Container
//		Propety Name: org.jruby.embed.class.path (or java.class.path) 
 * 
//		Classpaths are used to load Ruby scripts as well as Java classes and jar archives. 
 * 		Embed Core and BSF have a method to set classpath. However, JSR223 doesn't have such method 
 * 		or method argument. Thus, JSR223 need to be set classpath by system property. 
 * 		Either org.jruby.embed.class.path or java.class.path property names can be used to 
 * 		set classpath. The org.jruby.embed.class.path system property is avaiable to use in 
 * 		Embed Core and BSF, too. In case of Embed Core and JSR223, a value assigned to 
 * 		org.jruby.embed.class.path is looked up first, then java.class.path. 
 * 		This means that only org.jruby.embed.class.path is used if exists. 
 * 		As for BSF, after java.class.path is looked up, org.jruby.embed.class.path is added to 
 * 		if exists. The format of the paths is the same as Java's class path syntax, :(colon) 
 * 		separated path string on Unix and OS X or ;(semi-colon) separated one on Windows. 
 * 		Be sure to set classpaths before you instantiate javax.script.ScriptEngineManager or 
 * 		register engine to org.apache.bsf.BSFManager. A related topic is in ClasspathAndLoadPath
 * 		*/
	}
	
	public static ScriptEngine engine = loadJRuby();
	private static ScriptEngine loadJRuby(){
		try{
			try {
				ResourceUtil.loadJar(new File(J2SEContext.jrubyjarname));
			} catch (Exception e) {
				e.printStackTrace();
				LogManager.err( "Error load lib " + J2SEContext.jrubyjarname);
				return null;
			}

			buildEngine();
			return engine;  
		}catch (Exception e) {
			new Thread(){
				public void run(){
					JOptionPane.showMessageDialog(null, "Load JRuby lib error!", "Error", JOptionPane.ERROR_MESSAGE);
				}
			}.start();
		}
		return null;
	}

	private static void buildEngine() {
		ScriptEngineManager manager = new ScriptEngineManager();  
		engine = manager.getEngineByName("jruby");
		engine.getContext().setErrorWriter(errorWriter);
	}
	
	private static HCScriptException sexception = new HCScriptException("");
	
	/**
	 * 没有错误，返回null
	 * @return
	 */
	public static ScriptException getEvalException(){
		return isError?sexception:null;
	}
	
	public static void runLater(final String script, final Map map){
		DelayServer.getInstance().addDelayBiz(new AbstractDelayBiz(null) {
			@Override
			public void doBiz() {
				RubyExector.run(script, map);
			}
		});
	}
	
	public static final synchronized Object run(final String script, final Map map) {
		try {
			if(isError){
				errorWriter.reset();
				isError = false;
			}
			
			if(map == null){
				return engine.eval(script);
			}else{
				Bindings bindings = new SimpleBindings();
				bindings.put("_hcmap", map);
				return engine.eval(script, bindings);
			}
		} catch (ScriptException e) {
			isError = true;
			
			String err = errorWriter.getMessage();
			
			L.V = L.O ? false : LogManager.log("JRuby Script Error : " + err);
			
			char[] chars = err.toCharArray();
			for (int i = 0; i < chars.length; i++) {
				if(chars[i] == '\n'){
					sexception.setMessage(new String(chars, 0, i));
					break;
				}
			}
			
			Matcher matcher = linePattern.matcher(err);
			if(matcher.find()){
				final String group = matcher.group(1);
				sexception.setLineNumber(Integer.parseInt(group));
			}
			return null;
		}
	}

	public static Map<String, String> toMap(final HCURL _hcurl) {
		Map<String, String> map = null;
		final int size = _hcurl.getParaSize();
		if(size > 0){
			map = new HashMap<String, String>();
			for (int i = 0; i < size; i++) {
				final String key = _hcurl.getParaAtIdx(i);
				try {
					map.put(key, URLDecoder.decode(_hcurl.getValueofPara(key), IConstant.UTF_8));
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
		return map;
	}
	
	
}

class RubyWriter extends Writer{
	private static final int SIZE = 1024 * 1;
	final char[] bs = new char[SIZE];
	int writeIdx = 0;
	@Override
	public void write(char[] cbuf, int off, int len) throws IOException {
		if(writeIdx + len < SIZE){
			System.arraycopy(cbuf, off, bs, writeIdx, len);
			writeIdx += len;
		}
	}
	
	@Override
	public void flush() throws IOException {
	}
	
	@Override
	public void close() throws IOException {
	}
	
	public void reset(){
		writeIdx = 0;
	}

	public String getMessage(){
		if(writeIdx == 0){
			return "";
		}else{
			return new String(bs, 0, writeIdx);
		}
	}
}

class HCScriptException extends ScriptException{
	String message;
	int lineNumber;
	
	public HCScriptException(String message) {
		super(message);
	}
	
	public int getLineNumber() {
		return lineNumber;
	}
	
	public void setLineNumber(int ln) {
		lineNumber = ln;
	}
	
	public String getMessage() {
		return message;
	}
	
	public void setMessage(String message) {
		this.message = message;
	}
}
